﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Mvc;
using System.Linq.Dynamic;

namespace Softwarehouse.MvcCrud.JQuery
{
    public abstract class GenericDataGrid<TModel> : Controller
        where TModel : new()
    {
        public IQueryable<TModel> items;
        private readonly string gridName;
        private readonly string gridHeading;
        private readonly string jsonUrl;
        private readonly string deleteString;
        private readonly string editString;
        private readonly string primaryKey;
        private readonly string orderBy;
        private readonly string sortOrder;
        protected string extraGridSettings = "";
        protected string editHeading = "Edit";
        protected string deleteHeading = "Delete";
        protected string rowList = "20, 30, 50, 100";
        protected int rows = 20;
        protected string themeName = "redmond";
        protected string locale = "en";

        public string dateTimeFormat = "dd-MMM-yyyy";

        private readonly DataGridRow[] data_rows;

        protected GenericDataGrid(string gridName, string gridHeading, string jsonUrl, string editString, string deleteString, string primaryKey, string orderBy, DataGridRow[] data_rows)
        {
            this.gridName = gridName;
            this.gridHeading = gridHeading;

            this.jsonUrl = jsonUrl;
            this.editString = editString;
            this.deleteString = deleteString;

            this.data_rows = data_rows;

            this.primaryKey = primaryKey;

            string[] sorting = orderBy.Split(' ');
            this.orderBy = sorting[0];
            sortOrder = (sorting.Length > 1) ? sorting[1] : "asc";
        }

        public ActionResult GenerateGridData(string sidx, string sord, int page, int rows, bool _search, string searchField, string searchOper, string searchString)
        {
        //public JsonResult GenerateGridData(string sidx, string sord, int page, int num_rows)
        //{
            //Generate Variables
            int pageIndex = Convert.ToInt32(page) - 1;
            int pageSize = rows;
            int totalRecords = items.Count();
            int totalPages = (int)Math.Ceiling((float)totalRecords / pageSize);
          
            if (_search)
            {
                //check type of string
                Type final_type = getFinalType(searchField);

                object searchObject;
                if (final_type == typeof (string))
                {
                    searchObject = searchString;
                }
                else
                {
                    MethodInfo parse = final_type.GetMethod("Parse", new [] { typeof(string) });
                    searchObject = parse.Invoke(null, new object[] { searchString });
                }

                switch (searchOper)
                {
                    case ("eq"):
                        items = items.Where(string.Format("{0} == @0", searchField), searchObject);
                        break;
                    case ("ne"):
                        items = items.Where(string.Format("{0} != @0", searchField), searchObject);
                        break;
                    case ("lt"):
                        items = items.Where(string.Format("{0} < @0", searchField), searchObject);
                        break;
                    case ("le"):
                        items = items.Where(string.Format("{0} <= @0", searchField), searchObject);
                        break;
                    case ("gt"):
                        items = items.Where(string.Format("{0} > @0", searchField), searchObject);
                        break;
                    case ("ge"):
                        items = items.Where(string.Format("{0} >= @0", searchField), searchObject);
                        break;
                }
            }

            items = items
                .OrderBy(sidx + " " + sord)
                .Skip(pageIndex * pageSize)
                .Take(pageSize);
            //Generate JSON
            var jsonData = new
            {
                total = totalPages,
                page,
                records = totalRecords,
                rows = (items.Select(p => new
                {
                    //id column from repository
                    i = typeof(TModel).GetProperty(primaryKey).GetValue(p, null),
                    cell = getCells(p)
                })).ToArray()
            };
            return Json(jsonData);
        }

        protected virtual string editCell(TModel line)
        {
            if (!string.IsNullOrEmpty(editString))
            {

                PropertyInfo c = typeof (TModel).GetProperty("id");
                return string.Format("<a href='{0}' >Edit</a>",
                                     editString.Replace("?", c.GetValue(line, null).ToString()));
            }
            throw new NotImplementedException();
        }

        protected virtual string deleteCell(TModel line)
        {
            if (!string.IsNullOrEmpty(deleteString))
            {

                PropertyInfo c = typeof (TModel).GetProperty("id");
                return string.Format("<a href='{0}' >Delete</a>",
                                     deleteString.Replace("?", c.GetValue(line, null).ToString()));
            }
            throw new NotImplementedException();
        }

        private string[] getCells(TModel p)
        {
            List<string> result = new List<string>();

            try
            {
                result.Add(editCell(p));
            }
            catch (NotImplementedException)
            {}

            try
            {
                result.Add(deleteCell(p));
            }
            catch (NotImplementedException)
            { }

            foreach(string column in data_rows.Select(r => r.value))
            {
                try
                {
                    //hack for tblcategory.name
                    string[] parts = column.Split('.');

                    //Set first part
                    PropertyInfo c = typeof(TModel).GetProperty(parts[0]);
                    object tmp = c.GetValue(p, null);
                    
                    //loop through if there is more than one depth to the . eg tblCategory.name
                    for (int j = 1; j < parts.Length; j++)
                    {
                        c = tmp.GetType().GetProperty(parts[j]);
                        tmp = c.GetValue(tmp, null);
                    }

                    if (tmp.GetType() == typeof(DateTime))
                    {
                        result.Add(((DateTime)tmp).ToString(dateTimeFormat));
                    }
                    else
                    {
                        result.Add(tmp.ToString());
                    }
                }
                catch (Exception)
                {
                    result.Add("");
                }
            }
            return result.ToArray();
        }

        public string renderIncludes
        {
            get
            {
                return
                    string.Format(@"<link href=""/Scripts/css/ui.jqgrid.css"" rel=""stylesheet"" type=""text/css"" />
                    <link href=""/Scripts/css/{0}/jquery-ui-1.7.1.custom.css"" rel=""stylesheet"" type=""text/css"" />
                    <script src=""/Scripts/jquery-1.3.2.min.js"" type=""text/javascript""></script>
                    <script src=""/Scripts/i18n/grid.locale-{1}.js"" type=""text/javascript""></script>
                    <script src=""/Scripts/jquery.jqGrid.min.js"" type=""text/javascript""></script>", themeName, locale);
            }
        }

        public string renderGrid
        {
            get
            {
                return
                    string.Format(
                        @"<script type=""text/javascript"">
                    jQuery(document).ready(function() {{
                        jQuery('#{0}').jqGrid({{
                            url: '{1}',
                            datatype: 'json',
                            colNames: [{2}],
                            colModel: [{3}],
                            rowNum: {9},
                            rowList: [{8}],
                            imgpath: ""/Scripts/css/{10}/images"",
                            pager: jQuery('#{0}_pager'),
                            sortname: '{5}',
                            viewrecords: true,
                            sortorder: '{6}',
                            caption: '{4}'{7}
                        }}).navGrid('#{0}_pager', {{ edit: false, add: false, del: false, search: true, refresh: true }});
                    }}); 
                </script>
                <table id=""{0}"" width=""100%"" class=""scroll"" cellpadding=""0"" cellspacing=""0""></table>
                <div id=""{0}_pager"" class=""scroll"" style="" text-align:center;""></div>",
                        gridName, jsonUrl, headingsString, columnsString, gridHeading, orderBy, sortOrder,
                        extraGridSettings, rowList, rows, themeName);
            }
        }

        public string headingsString
        {
            get
            {
                string result = "";
                string delimiter = "";

                if (!string.IsNullOrEmpty(editString))
                {
                    result += string.Format("{0}'{1}'", delimiter, editHeading);
                    delimiter = ", ";
                }

                if (!string.IsNullOrEmpty(deleteString))
                {
                    result += string.Format("{0}'{1}'", delimiter, deleteHeading);
                    delimiter = ", ";
                }

                foreach(string s in data_rows.Select(r => r.heading))
                {
                    result += string.Format("{0}'{1}'", delimiter, s);
                    delimiter = ", ";
                }
                return result;
            }
        }

        public string columnsString
        {
            get
            {
                string result = "";
                string delimiter = "";

                if (!string.IsNullOrEmpty(editString))
                {
                    result += string.Format("{0}{{ name: 'edit', index: 'edit', width:40, sortable:false, search:false }}", delimiter);
                    delimiter = ", ";
                }

                if (!string.IsNullOrEmpty(deleteString))
                {
                    result += string.Format("{0}{{ name: 'delete', index: 'delete', width:80, sortable:false, search:false }}", delimiter);
                    delimiter = ", ";
                }
                foreach (DataGridRow row in data_rows)
                {
                    result += string.Format("{0}{{ name: '{1}', index: '{1}', width:{2} {3} }}", delimiter, row.id, row.width, string.IsNullOrEmpty(row.extraArguments) ? "" : (string.Format(", {0}", row.extraArguments)));
                    delimiter = ", ";
                }
                return result;
            }
        }

        private static Type getFinalType(string input)
        {
            //hack for tblcategory.name
            string[] parts = input.Split('.');

            //Set first part
            PropertyInfo c = typeof(TModel).GetProperty(parts[0]);

            //loop through if there is more than one depth to the . eg tblCategory.name
            for (int j = 1; j < parts.Length; j++)
            {
                c = c.PropertyType.GetProperty(parts[j]);
            }
            return c.PropertyType;

        }
    }

    public class DataGridRow
    {
        public string value;
        public string id;
        public string heading;
        public int width;
        public string extraArguments;

        public DataGridRow(string value, string id, string heading, int width, string extraArguments )
        {
            this.value = value;
            this.id = id;
            this.heading = heading;
            this.width = width;
            this.extraArguments = extraArguments;
        }

        public DataGridRow(string value, string id, string heading) : this(value, id, heading, 55, "") { }
        public DataGridRow(string value, string id, string heading, int width) : this(value, id, heading, width, "") { }

        public DataGridRow(string value, string heading) : this(value, value, heading, 55, "") { }
        public DataGridRow(string value, string heading, int width) : this(value, value, heading, width, "") { }
        public DataGridRow(string value, string heading, int width, string extraArguments) : this(value, value, heading, width, extraArguments) { }
    }
}
